package org.helgoboss.quick_launcher_ref_impl

import scala.swing._
import scala.swing.event._
import javax.swing.event.{DocumentListener, DocumentEvent, TableModelListener, TableModelEvent}
import javax.swing.UIManager
import javax.swing.table.TableModel
import org.helgoboss.scala_swing_additions.MigPanel
import org.helgoboss.scala_additions.RichFile
import java.awt.Toolkit
import java.io._
import org.helgoboss.commandlet.ExecutionContext
import org.helgoboss.launchlet.{LaunchletExecutionContext, Launchlet}
import collection.JavaConversions

abstract class QuickLauncherView {
    var _currentExecutionContext: LaunchletExecutionContext = emptyExecutionContext

    val emptyExecutionContext = new LaunchletExecutionContext {
        import JavaConversions._

        val arguments = Nil
        val environment = System.getenv.toMap
        def inputStream = None
        def outputStream = None
        def workingDirectory = None
    }

    private lazy val launchletTable = new Table {        
        model = launchletTableModel
        listenTo(keys)
        reactions += {                
            case KeyPressed(_, Key.Escape, _, _) =>
                hide
        }
    }
    
    private lazy val launchletTableModel = new TableModel {
        private val listeners = new collection.mutable.HashSet[TableModelListener]
        
        def updateData {
            listeners.foreach(_.tableChanged(new TableModelEvent(this)))
        }
        
        def addTableModelListener(listener: TableModelListener) {
            listeners += listener
        }
        
        def removeTableModelListener(listener: TableModelListener) {
            listeners -= listener
        }
        
        def getColumnClass(columnIndex: Int) = {    
            columnIndex match {
                case 0 => classOf[String]
            }
        }
        
        def getColumnCount = 1
        
        def getColumnName(columnIndex: Int) = {
            columnIndex match {
                case 0 => "Name"
            }
        }
        
        def getRowCount = launchlets.size
        
        def getValueAt(rowIndex: Int, columnIndex: Int) = {
            val launchlet = launchlets(rowIndex)
            columnIndex match {
                case 0 => launchlet.name
            }
        }
        
        def isCellEditable(rowIndex: Int, columnIndex: Int) = false
        
        def setValueAt(value: AnyRef, rowIndex: Int, columnIndex: Int) {
        }
    }
    
    private lazy val currentExecutionContextLabel: Label = new Label
    
    private lazy val launchletTextField: TextField = new TextField(50) {
        focusable = true
        peer.getDocument addDocumentListener new DocumentListener {
            def changedUpdate(e: DocumentEvent) {
                updateLaunchletTable
            }
            def insertUpdate(e: DocumentEvent) {
                updateLaunchletTable
            }
            def removeUpdate(e: DocumentEvent) {
                updateLaunchletTable
            }
        }
        listenTo(keys)
        reactions += {
            case KeyPressed(_, Key.Up, _, _) =>
                jumpToPreviousLaunchlet
                
            case KeyPressed(_, Key.Down, _, _) =>
                jumpToNextLaunchlet
                
            case KeyPressed(_, Key.Enter, _, _) =>
                executeSelectedLaunchlet
                
            case KeyPressed(_, Key.Escape, _, _) =>
                hide
        }
    }
    
    private var launchlets: Seq[Launchlet] = Nil    
    
    
    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName)
    
    private lazy val frame = new MainFrame {
        title = "Quick Launcher"
        preferredSize = new Dimension(800, 500)
        val screenSize = Toolkit.getDefaultToolkit.getScreenSize 
        
        location = new Point(
            (screenSize.getWidth / 2 - preferredSize.getWidth / 2).asInstanceOf[Int], 
            (screenSize.getHeight / 2 - preferredSize.getHeight / 2).asInstanceOf[Int]
        )
        contents = new MigPanel("wrap 1", "[grow,fill]", "[fill][grow,fill]") {
            this add launchletTextField
            this add new ScrollPane {
                viewportView = launchletTable
            }
            this add currentExecutionContextLabel
        }
        
        override def closeOperation { 
            hide
        }
    }
     
    protected def findLaunchlets(searchString: String): Seq[Launchlet]
     

    private def executeSelectedLaunchlet {
        val ec = currentExecutionContext
        new Thread {
            override def run {
                selectedLaunchlet.foreach { l =>
                    l.execute(ec)
                }
            }
        }.start
        hide
    }
    
    def currentExecutionContext_=(context: LaunchletExecutionContext) {
        synchronized {
            _currentExecutionContext = context
        }
        Swing.onEDT {
            currentExecutionContextLabel.text = "<html><ul><li><b>Arguments:</b> " + context.arguments.mkString("\"", "\" \"", "\"") +
                    "</li><li><b>Working Directory:</b> " + context.workingDirectory + "</li></ul></html>"
        }
    }
    
    def currentExecutionContext = _currentExecutionContext
        
    /**
     * Does a complete refresh.
     */
    def update {
        Swing.onEDT {
            updateLaunchletTable
        }
    }
    
    private def jumpToNextLaunchlet {
        val newIndex = selectedIndex match {
            case Some(i) => i + 1
            case None => 0
        }
        tryToSelectRow(newIndex)
    }
    
    private def jumpToPreviousLaunchlet {
        val newIndex = selectedIndex match {
            case Some(i) => i - 1
            case None => launchlets.size - 1
        }
        tryToSelectRow(newIndex)
    }
    
    /**
     * Tries to select the row at the given position in the table.
     */
    private def tryToSelectRow(rowIndex: Int) {
        if (rowIndex < launchlets.size) {
            launchletTable.selection.rows.clear
            launchletTable.selection.rows += rowIndex
        }
    }
    
    /**
     * Tries to select the given launchlet in the table.
     */
    private def tryToSelect(launchlet: Launchlet) {
        val rowIndex = launchlets.indexOf(launchlet)
        if (rowIndex >= 0) {
            tryToSelectRow(rowIndex)
        }
    }
    
    
    /**
     * Returns the selected launchlet
     */
    private def selectedLaunchlet = {
        selectedIndex.map(launchlets(_))
    }
    
    /**
     * Returns the selected index
     */
    private def selectedIndex = {
        val selectedIndices = launchletTable.selection.rows
        if (selectedIndices.size > 0 && selectedIndices.head < launchlets.size) {
            Some(selectedIndices.head)
        } else {
            None
        }
    }
    
    /**
     * Does the search and refreshes the table content. It tries to keep the current selection.
     */
    private def updateLaunchletTable {
        val previouslySelectedLaunchlet = selectedLaunchlet
        launchlets = findLaunchlets(launchletTextField.text)
        launchletTableModel.updateData
        launchletTable.revalidate
        previouslySelectedLaunchlet.foreach(tryToSelect)
        if (selectedIndex.isEmpty) {
            tryToSelectRow(0)
        }
    }
    
    def toggleVisibility {
        if (isInForeground) {
            /* Quick launcher is in foreground. Hide it. */
            hide
        } else {
            makeSureIsInFront
        }
    }
    
    def makeSureIsInFront {
        if(frame.visible) {
            /* Quick launcher is not in foreground but visible. Bring it to foreground. */
            hide
            show
        } else {
            /* Quick launcher is not visible. Show it. */
            show
        }
    }
        
    def show {
        Swing.onEDT {
            /* Load frame */
            frame
            updateLaunchletTable
            launchletTextField.requestFocus
            launchletTextField.selectAll
            frame.visible = true
        }
    }
    
    def hide {
        currentExecutionContext = emptyExecutionContext
        Swing.onEDT {
            frame.visible = false
        }
    }
    
    def dispose {
        Swing.onEDT {
            frame.dispose
        }
    }
    
    private def isInForeground = launchletTable.hasFocus || launchletTextField.hasFocus

}
